package com.wwz.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

@Component
public class RedisLuaTemplate {
    private final  Logger log = LoggerFactory.getLogger(this.getClass());
    private final  String expireTime = "120000";

    @SuppressWarnings("rawtypes")
    @Resource(name="masterStringRedisTemplate")
    private StringRedisTemplate stringRedisTemplateDemo;
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate slaveRedisTemplateDemo;

    private DefaultRedisScript<String> getLockRedisScript;
    private DefaultRedisScript<String> releaseLockRedisScript;
    private DefaultRedisScript<String> incrementRedisScript;
    private DefaultRedisScript<String> selectRedisScript;
    private DefaultRedisScript<String> removeRedisScript;
    private DefaultRedisScript<String> getRedisScript;

    private StringRedisSerializer argsStringSerializer = new StringRedisSerializer();
    private StringRedisSerializer resultStringSerializer = new StringRedisSerializer();



    private final String EXEC_RESULT = "1";
    @SuppressWarnings("unchecked")
    @PostConstruct
    private void init() {
        getLockRedisScript = new DefaultRedisScript<String>();
        getLockRedisScript.setResultType(String.class);
        incrementRedisScript = new DefaultRedisScript<String>();
        incrementRedisScript.setResultType(String.class);

        selectRedisScript = new DefaultRedisScript<String>();
        selectRedisScript.setResultType(String.class);

        removeRedisScript = new DefaultRedisScript<String>();
        removeRedisScript.setResultType(String.class);

        getRedisScript = new DefaultRedisScript<String>();
        getRedisScript.setResultType(String.class);

        getLockRedisScript.setResultType(String.class);
        releaseLockRedisScript = new DefaultRedisScript<String>();
        releaseLockRedisScript.setResultType(String.class);

        // 初始化装载 lua 脚本
        getLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/lock.lua")));
        releaseLockRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/releaseLock.lua")));
        incrementRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/incre.lua")));
        selectRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/mult.lua")));
        removeRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/removeKey.lua")));
        getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/get.lua")));

    }

    /**
     * 加锁操作
     * @param key Redis 锁的 key 值
     * @param requestId 请求id，防止解了不该由自己解的锁 (随机生成)
     * @param retryTimes 获取锁的重试次数
     * @return true or false
     */

    @SuppressWarnings("unchecked")
    public boolean lock(String key, String requestId, int retryTimes) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(getLockRedisScript, argsStringSerializer, resultStringSerializer,
                        Collections.singletonList(key), requestId, expireTime);
                log.debug("result:{},type:{}", result, result.getClass().getName());
                if (EXEC_RESULT.equals(result)) {
                    /*executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            String threadName = Thread.currentThread().getName();
                            try {
                                log.debug(threadName+"线程等待中。。。");
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }finally {
                                log.debug(threadName+"线程自动解锁。。。");
                                unLock(key, requestId);
                            }

                        }
                    });*/
                    log.debug("获取锁成功。。。");
                    return true;
                } else {
                    if(retryTimes==-1){
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(2000);
                        continue;
                    }
                    count++;
                    if (retryTimes == count) {
                        log.warn("has tried {} times , failed to acquire lock for key:{},requestId:{}", count, key, requestId);
                        return false;
                    } else {
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(2000);
                        continue;
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            unLock(key, requestId);
        }
        return false;
    }

    /**
     * 解锁操作
     * @param key Redis 锁的 key 值
     * @param requestId 请求 id, 防止解了不该由自己解的锁 (随机生成)
     * @return true or false
     */
    @SuppressWarnings("unchecked")
    public boolean unLock(String key, String requestId) {
        String result = stringRedisTemplateDemo.execute(releaseLockRedisScript, argsStringSerializer, resultStringSerializer,
                Collections.singletonList(key), requestId);
        if (EXEC_RESULT.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 主节点进行写入
     * @param key
     * @param requestId
     * @param key1
     * @param retryTimes
     * @param expireTime
     * @return
     */
    public boolean increment(String key,String requestId,String key1, int retryTimes,String expireTime) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(incrementRedisScript, argsStringSerializer, resultStringSerializer,
                        Lists.newArrayList(key,key1),requestId, expireTime);
                log.debug("result:{},type:{}", result, result.getClass().getName());
                if (EXEC_RESULT.equals(result)) {
                    return true;
                } else {
                    if(result.equals("-1")){
                        return false;
                    }
                    if(retryTimes==-1){
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(1000);
                        continue;
                    }
                    count++;
                    if (retryTimes == count) {
                        log.warn("has tried {} times , failed to acquire lock for key:{},requestId:{}", count, key, requestId);
                        return false;
                    } else {
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(1000);
                        continue;
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            unLock(key, requestId);
        }
        return false;
    }

    /**
     * 从节点进行写入
     * @param key
     * @param requestId
     * @param key1
     * @param retryTimes
     * @param expireTime
     * @return
     */
    public boolean incrementBySlave(String key,String requestId,String key1, int retryTimes,String expireTime) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(incrementRedisScript, argsStringSerializer, resultStringSerializer,
                        Lists.newArrayList(key,key1),requestId, expireTime);
                log.debug("result:{},type:{}", result, result.getClass().getName());
                if (EXEC_RESULT.equals(result)) {
                    return true;
                } else {
                    if(result.equals("-1")){
                        return false;
                    }
                    if(retryTimes==-1){
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(1000);
                        continue;
                    }
                    count++;
                    if (retryTimes == count) {
                        log.warn("has tried {} times , failed to acquire lock for key:{},requestId:{}", count, key, requestId);
                        return false;
                    } else {
                        log.warn("try to acquire lock {} times for key:{},requestId:{}", count, key, requestId);
                        Thread.sleep(1000);
                        continue;
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            unLock(key, requestId);
        }
        return false;
    }


    public List<String> select(String key,int size) {
                List<String> result = slaveRedisTemplateDemo.opsForList().range(key,0,size);
        return result;
    }


    public Set<String> keys(String keyPrefix) {
        String realKey = "*" + keyPrefix + "*";
        try {
            return ( Set<String> )redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
                Set<String> binaryKeys = new HashSet<>();
                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(realKey).count(Integer.MAX_VALUE).build());
                while (cursor.hasNext()) {
                     binaryKeys.add(new String(connection.get(cursor.next())));
                }
                return binaryKeys;
            });
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }



    public boolean removeKey(String key,int size,int retryTimes) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(removeRedisScript, argsStringSerializer, resultStringSerializer,
                        Lists.newArrayList(key),size+"");
                log.debug("result:{},type:{}", result, result.getClass().getName());
                if ("ok".equals(result.toLowerCase())) {
                    return true;
                } else {
                    if(result.equals("-1")){
                        return false;
                    }
                    if(retryTimes==-1){
                        log.warn("try to acquire lock {} times for key:{}", count, key);
                        Thread.sleep(1000);
                        continue;
                    }
                    count++;
                    if (retryTimes == count) {
                        log.warn("has tried {} times , failed to acquire lock for key:{}}", count, key);
                        return false;
                    } else {
                        log.warn("try to acquire lock {} times for key:{}", count, key);
                        Thread.sleep(1000);
                        continue;
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }


    public boolean isHaveProduct(String key,int retryTimes) {
        try {
            int count = 0;
            while (true) {
                String result = stringRedisTemplateDemo.execute(getRedisScript, argsStringSerializer, resultStringSerializer,
                        Lists.newArrayList(key));
                if (EXEC_RESULT.equals(result)) {
                    return true;
                } else {
                    if (result.equals("-1")) {
                        throw new Exception("商品抢购完了");
                    }
                    if (retryTimes == -1) {
                        log.warn("try to acquire lock {} times for key:{}", key);
                        Thread.sleep(1000);
                        continue;
                    }
                    count++;
                    if (retryTimes == count) {
                        log.warn("has tried {} times , failed to acquire lock for key:{}}", count, key);
                        return false;
                    } else {
                        log.warn("try to acquire lock {} times for key:{}", count, key);
                        Thread.sleep(1000);
                        continue;
                    }
                }
            }
            }catch(Exception e){
                log.error(e.getMessage());
            }
        return false;
    }

}
